﻿#pragma warning disable CS0660, CS0661
using System;

namespace Snake.Misc
{
    public class EasyBigNumber
    {
        public static Func<decimal, long, string> OnToString;

        //科学记数法前半部分
        private decimal _real;

        //指数部分
        private long _digits;

        //保留精度，decimal 最大精度28位
        private const int Precision = 24;

        public EasyBigNumber(string value)
        {
            value = value.Trim();
            //科学计数法
            if (value.Contains('e') || value.Contains('E'))
            {
                var list = value.Split('e', 'E');
                if (list.Length > 2)
                {
                    throw new FormatException("字符串格式有误");
                }

                try
                {
                    _real = Convert.ToDecimal(list[0]);
                    _digits = long.Parse(list[1]);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                    throw;
                }
            }
            else
                //纯字符串有小数点
            if (value.Contains('.'))
            {
                var list = value.Split('.');
                if (list.Length > 2)
                {
                    throw new FormatException("字符串格式有误");
                }

                try
                {
                    _digits = list[0].Length - 1;
                    _real = Convert.ToDecimal(value.Replace(".", "").Insert(1, "."));
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                    throw;
                }
            }
            else
                //没有小数点
            {
                _digits = value.Length - 1;
                _real = Convert.ToDecimal(value.Insert(1, "."));
            }

            Trim();
        }

        public EasyBigNumber(EasyBigNumber value)
        {
            _real = value._real;
            _digits = value._digits;
            Trim();
        }

        public EasyBigNumber(int real, long digits = 0)
        {
            _real = Convert.ToDecimal(real);
            _digits = digits;
            Trim();
        }

        public EasyBigNumber(double real, long digits = 0)
        {
            _real = Convert.ToDecimal(real);
            _digits = digits;
            Trim();
        }

        public EasyBigNumber(decimal real, long digits = 0)
        {
            _real = real;
            _digits = digits;
            Trim();
        }

        public EasyBigNumber Add(EasyBigNumber other)
        {
            var offset = other._digits - _digits;
            if (offset < -Precision)
            {
                return new EasyBigNumber(this);
            }

            if (offset > Precision)
            {
                return new EasyBigNumber(other);
            }

            return new EasyBigNumber(_real * Convert.ToDecimal(Math.Pow(0.1, offset)) + other._real, other._digits);
        }

        public EasyBigNumber Sub(EasyBigNumber other)
        {
            var offset = other._digits - _digits;
            if (offset < -Precision)
            {
                return new EasyBigNumber(this);
            }

            if (offset > Precision)
            {
                var result = new EasyBigNumber(other);
                result._real *= -1;
                return result;
            }

            return new EasyBigNumber(_real * Convert.ToDecimal(Math.Pow(0.1, offset)) - other._real, other._digits);
        }

        public EasyBigNumber Mul(EasyBigNumber other)
        {
            return new EasyBigNumber(_real * other._real, _digits + other._digits);
        }

        public EasyBigNumber Div(EasyBigNumber other)
        {
            return new EasyBigNumber(_real / other._real, _digits - other._digits);
        }

        public override string ToString()
        {
            return OnToString != null ? OnToString.Invoke(_real, _digits) : $"{_real}e{_digits}";
        }

        private void Trim()
        {
            if (_real == 0)
            {
                _digits = 0;
                return;
            }

            var abs = Math.Abs(_real);
            var digits = (long)Math.Log10(Convert.ToDouble(abs)) + _digits;
            var mod = digits - _digits;
            _real *= Convert.ToDecimal(Math.Pow(0.1, mod));
            _digits = digits;
        }

        public static implicit operator EasyBigNumber(int val)
        {
            return new EasyBigNumber(val);
        }

        public static implicit operator EasyBigNumber(double val)
        {
            return new EasyBigNumber(val);
        }

        public static implicit operator EasyBigNumber(decimal val)
        {
            return new EasyBigNumber(val);
        }

        public static implicit operator float(EasyBigNumber easyBig)
        {
            return (float)(easyBig._real * Convert.ToDecimal(Math.Pow(10, easyBig._digits)));
        }

        public static implicit operator double(EasyBigNumber easyBig)
        {
            return (double)(easyBig._real * Convert.ToDecimal(Math.Pow(10, easyBig._digits)));
        }

        public static EasyBigNumber operator +(EasyBigNumber left, EasyBigNumber right)
        {
            return left.Add(right);
        }

        public static EasyBigNumber operator -(EasyBigNumber left, EasyBigNumber right)
        {
            return left.Sub(right);
        }

        public static EasyBigNumber operator *(EasyBigNumber left, EasyBigNumber right)
        {
            return left.Mul(right);
        }

        public static EasyBigNumber operator /(EasyBigNumber left, EasyBigNumber right)
        {
            return left.Div(right);
        }

        public static bool operator >(EasyBigNumber left, EasyBigNumber right)
        {
            return left._digits > right._digits ||
                   left._digits == right._digits && left._real > right._real;
        }

        public static bool operator >=(EasyBigNumber left, EasyBigNumber right)
        {
            return left._digits > right._digits ||
                   left._digits == right._digits && left._real >= right._real;
        }

        public static bool operator <(EasyBigNumber left, EasyBigNumber right)
        {
            return left._digits < right._digits ||
                   left._digits == right._digits && left._real < right._real;
        }

        public static bool operator <=(EasyBigNumber left, EasyBigNumber right)
        {
            return left._digits < right._digits ||
                   left._digits == right._digits && left._real <= right._real;
        }

        public static bool operator ==(EasyBigNumber left, EasyBigNumber right)
        {
            if (ReferenceEquals(left, null) || ReferenceEquals(right, null))
            {
                return false;
            }

            return left._digits == right._digits && left._real == right._real;
        }

        public static bool operator !=(EasyBigNumber left, EasyBigNumber right)
        {
            if (ReferenceEquals(left, null) || ReferenceEquals(right, null))
            {
                return false;
            }

            return left._digits != right._digits || left._real != right._real;
        }
    }
}